﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.ServiceProcess;
using System.Security.Permissions;
using System.Text;
using SharpPcap;
using PacketDotNet;
using ARSoft.Tools.Net.Dns;

namespace DnsCaptureService
{
    internal static class DnsCapture
    {
        private static string _path = ConfigurationManager.AppSettings["DestinationLogDirectory"].ToString() + System.DateTime.Today.ToString("yyyyMMdd") + "-DnsQueryLog-" + System.DateTime.Today.ToString("ddd") +".log";
                
        private static DateTime _rolltime; 
        private static DateTime _lastroll = System.DateTime.UtcNow;
        private static StreamWriter sw = new StreamWriter(_path, true, Encoding.ASCII);
        //private static byte _configflag = Convert.ToByte(ConfigurationManager.AppSettings["LogLevel"]);
        //private static string _qstub = "Q:";
        //private static string _astub = "A:";
        private static LivePcapDeviceList _devices = LivePcapDeviceList.Instance;
        private static int i = int.Parse(ConfigurationManager.AppSettings["DeviceId"].ToString());
        //private static byte _debugflag = byte.Parse(ConfigurationManager.AppSettings["DebugFlag"].ToString());
        private static bool BackgroundThreadStop = false;
        private static object QueueLock = new object();
        private static List<PacketDotNet.RawPacket> PacketQueue = new List<PacketDotNet.RawPacket>();
        private static System.Threading.Thread backgroundThread = new System.Threading.Thread(BackgroundThread);
         
        internal static void Start()
        {
                       
            /*If no device exists, print error */
            if (_devices.Count < 1)
            {
                //EventLog.WriteEntry("DNS Capture Service Startup: No device found on this machine");
                return;
            }
            try
            {
                
                

                // If the service is started on the same day after the roll time,
                // Put the roll time at the next day
                // Otherwise put it on the same day
                if (Convert.ToDateTime(ConfigurationManager.AppSettings["RolloverTime"]).Hour < System.DateTime.UtcNow.Hour)
                {
                    _rolltime = System.DateTime.UtcNow.Date.AddDays(1).AddHours(Convert.ToDateTime(ConfigurationManager.AppSettings["RolloverTime"]).Hour).AddMinutes(Convert.ToDateTime(ConfigurationManager.AppSettings["RolloverTime"]).Minute); 
                }
                else
                {
                    _rolltime = System.DateTime.UtcNow.Date.AddHours(Convert.ToDateTime(ConfigurationManager.AppSettings["RolloverTime"]).Hour).AddMinutes(Convert.ToDateTime(ConfigurationManager.AppSettings["RolloverTime"]).Minute); 
             
                }
                // check if log is new or appended, add header line
                FileInfo fi = new FileInfo(_path);
                if (fi.Length == 0)
                {
                    sw.WriteLine("DateTime,Protocol,TTL,Length,SourceIP,SourcePort,DestIP,DestPort,AuthoritativeAnswer,ReturnCode,Question,Answer,AddlRecords");
                    //sw.WriteLine("roll time" + _rolltime);
                    sw.Flush();                    
                }

                // set the last roll time to the file creation time
                // if the service is restarted then we want the earlier time
                // if it is a new file, then the creation time will still be what we want
                 _lastroll = fi.CreationTimeUtc;
                 //sw.WriteLine("last roll" + _lastroll);
                 sw.Flush();
                fi = null;


                //Register handler function to the 'packet arrival' event
                _devices[i].OnPacketArrival +=
                    new PacketArrivalEventHandler(device_OnPacketArrival);

                // Open the device for capturing
                int readTimeoutMilliseconds = int.Parse(ConfigurationManager.AppSettings["ReadTimeout"].ToString());
                var device = _devices[i];
                device.Open(DeviceMode.Normal, readTimeoutMilliseconds);
                //_devices[i].KernelBufferSize = int.Parse(ConfigurationManager.AppSettings["KernelBufferSize"].ToString()); //2097152;
                //device.MinToCopy = int.Parse(ConfigurationManager.AppSettings["MinToCopy"].ToString()); 
                device.NonBlockingMode = true;
                //tcpdump filter configuration
                string filter = ConfigurationManager.AppSettings["CaptureFilter"].ToString();
                _devices[i].Filter = filter;

                // Start background thread, then start capture
                //if (_debugflag > 1)
                //{
                    backgroundThread.Start();
               // }
                _devices[i].StartCapture();
            }
            catch (Exception ex)
            {
                //System.Diagnostics.EventLog.WriteEntry("DNSCaptureService", "DNS Capture Service Startup: " + ex.Message + "\n\r" + ex.StackTrace);\
                //sw.WriteLine(ex.Message + "\n\r" + ex.StackTrace);

            }
            finally
            {
                //sw.Flush();
                //sw.Close();
            }
        }

        internal static void Stop()
        {
            try
            {
                _devices[i].StopCapture();


                // ask the background thread to shut down
                BackgroundThreadStop = true;
                // wait for the background thread to terminate
                backgroundThread.Join();

                // Close the pcap device
                _devices[i].Close();
            }
            catch (Exception ex)
            {
                //sw.WriteLine(ex.Message + "\n\r" + ex.StackTrace);
            }
            finally
            {
                sw.Flush();
                sw.Close();
            }

        }

        /// <summary>
        /// Prints the time, length, src ip, src port, dst ip and dst port
        /// for each TCP/IP packet received on the network
        /// </summary>
        private static void device_OnPacketArrival(object sender, CaptureEventArgs e)
        {
            //if (_debugflag > 1)
            //{
                // lock QueueLock to prevent multiple threads accessing PacketQueue at
                // the same time
                lock (QueueLock)
                {
                    PacketQueue.Add(e.Packet);
                }
            //}

        }

        private static void BackgroundThread()
        {
            while (!BackgroundThreadStop)
            {
                bool shouldSleep = true;
                //if (_debugflag > 1)
                //{
                    lock (QueueLock)
                    {

                        if (PacketQueue.Count != 0)
                        {
                            shouldSleep = false;
                            //sw.WriteLine("Count:" + PacketQueue.Count);
                            //sw.Flush();
                        }
                    }
                //}

                if (shouldSleep)
                {
                    System.Threading.Thread.Sleep(250);
                }
                else // should process the queue
                {
                    List<RawPacket> ourQueue;
                    //if (_debugflag > 1)
                    //{
                        lock (QueueLock)
                        {
                            //debugging code
                            //sw.WriteLine("Count:" + PacketQueue.Count);
                            // swap queues, giving the capture callback a new one
                            ourQueue = PacketQueue;
                            PacketQueue = new List<RawPacket>();
                        }


                        //if (_debugflag > 2)
                        //{
                            System.Text.ASCIIEncoding encoding =  new System.Text.ASCIIEncoding();

                            foreach (var packet in ourQueue)
                            {
                                var time = packet.Timeval.Date;
                                var len = packet.Data.Length;

                                // compare configured roll time to packet time, if it is in the same hour
                                // and the last roll time is more than an hour away, then roll the log file
                                if (time.Hour == _rolltime.Hour & time.Subtract(_lastroll).TotalHours > 1)
                                {
                                    sw.Flush();
                                    sw.Close();
                                    sw = null;
                                    if (_rolltime.Hour == 0)
                                    {
                                        _path = ConfigurationManager.AppSettings["DestinationLogDirectory"].ToString() + System.DateTime.UtcNow.ToString("yyyyMMdd") + "-DnsQueryLog-" + System.DateTime.UtcNow.ToString("ddd") + ".log";
                                    }
                                    else
                                    {
                                        _path = ConfigurationManager.AppSettings["DestinationLogDirectory"].ToString() + System.DateTime.UtcNow.ToString("yyyyMMdd-HH") + "-DnsQueryLog-" + System.DateTime.UtcNow.ToString("ddd") + ".log";
                                    }
                                    sw = new StreamWriter(_path, false, Encoding.ASCII, 8192);
                                    //if (packet != null)
                                    //{
                                    //    var basepacket = Packet.ParsePacket(packet.LinkLayerType, packet.Timeval, packet.Data);
                                    //    var ipPacket = (PacketDotNet.IpPacket)basepacket.PayloadPacket;
                                    //    sw.WriteLine("Source IP: " + ipPacket.SourceAddress.ToString() + ",Port 53");
                                    //}
                                    sw.WriteLine("DateTime,Protocol,TTL,Length,SourceIP,SourcePort,DestIP,DestPort,AuthoritativeAnswer,ReturnCode,Question,Answer,AddlRecords");
                                    _lastroll = _rolltime;
                                    _rolltime = System.DateTime.UtcNow.Date.AddDays(1).AddHours(Convert.ToDateTime(ConfigurationManager.AppSettings["RolloverTime"]).Hour).AddMinutes(Convert.ToDateTime(ConfigurationManager.AppSettings["RolloverTime"]).Minute);

                                }

                                try
                                {
                                    //if (_debugflag > 3)
                                    //{
                                        if (packet != null)
                                        {
                                            var basepacket = Packet.ParsePacket(packet.LinkLayerType, packet.Timeval, packet.Data);
                                            var ipPacket = (PacketDotNet.IpPacket)basepacket.PayloadPacket;
                                            System.Net.IPAddress srcIp = ipPacket.DestinationAddress;
                                            System.Net.IPAddress dstIp = ipPacket.DestinationAddress;
                                            int ttl = ipPacket.TimeToLive;
                                            string protocol = ipPacket.Protocol.ToString();
                                            switch(protocol)
                                            {
                                                case "UDP":
                                                    var udpPacket = PacketDotNet.UdpPacket.GetEncapsulated(Packet.ParsePacket(packet.LinkLayerType, packet.Timeval, packet.Data));
                                                    if (udpPacket != null)
                                                    {
                                                        ushort dstPort = udpPacket.DestinationPort;
                                                        ushort srcPort = udpPacket.SourcePort;
                                                        //if (_debugflag > 4)
                                                        //{ 
                                                            DnsMessage response = (DnsMessage)DnsMessageBase.Create(udpPacket.PayloadData);
                                                            //if (_debugflag > 5)                                                         
                                                            //{
                                                                StringBuilder _qrecord = new StringBuilder();

                                                                    foreach (var question in response.Questions)
                                                                    {
                                                                        _qrecord.Append(question.Name);
                                                                        _qrecord.Append(";");
                                                                    }
                                                                    _qrecord.Append(",");
                                                                    
                                                                    foreach (var answer in response.AnswerRecords)
                                                                    {
                                                                        _qrecord.Append(answer.ToString());
                                                                        _qrecord.Append("|");
                                                                        _qrecord.Append(";");
                                                                    }
                                                                    _qrecord.Append(",");
                                                                    foreach (var add in response.AdditionalRecords)
                                                                    {
                                                                        _qrecord.Append(add.ToString());
                                                                        _qrecord.Append("|");
                                                                        _qrecord.Append(";");
                                                                    }
                                                                    //if (_debugflag > 6)
                                                                    //{
                                                                    sw.Write("{0},{1},{2},{3},{4},{5},{6},{7},{8},{9},{10}\n",
                                                                        time.ToString("MM/dd/yyyy HH:mm:ss.fff"), protocol,ttl, len, srcIp, srcPort, dstIp, dstPort, response.IsAuthoritiveAnswer, response.ReturnCode.ToString(), _qrecord);
                                                                    //}  
                                                                         
                                                                
                                                                _qrecord = null;
                                                            //} 
                                                        //}
                                                        udpPacket = null;
                                                    
                                                    }
                                                 break;
                                                case "TCP":
                                                    var tcpPacket = PacketDotNet.TcpPacket.GetEncapsulated(basepacket);
                                                    if (tcpPacket != null)
                                                    {
                                                        ushort srcPort = tcpPacket.SourcePort;
                                                        ushort dstPort = tcpPacket.DestinationPort;
                                                        //if (_debugflag > 4)
                                                        //{ 
                                                            DnsMessage response = (DnsMessage)DnsMessageBase.Create(tcpPacket.PayloadData);
                                                            //if (_debugflag > 5)                                                         
                                                            //{
                                                                StringBuilder _qrecord = new StringBuilder();
                                                                //if ((_configflag ^ 2) == 0) //&& response.Answers.Length != 0)
                                                                //{

                                                                    foreach (var question in response.Questions)
                                                                    {
                                                                        _qrecord.Append(question.Name);
                                                                        _qrecord.Append(";");
                                                                    }
                                                                    _qrecord.Append(",");
                                                                    
                                                                    foreach (var answer in response.AnswerRecords)
                                                                    {
                                                                        _qrecord.Append(answer.ToString());
                                                                        _qrecord.Append("|"); 
                                                                        _qrecord.Append(";");
                                                                    }
                                                                    _qrecord.Append(",");
                                                                    foreach (var add in response.AdditionalRecords)
                                                                    {
                                                                        _qrecord.Append(add.ToString());
                                                                        _qrecord.Append("|");
                                                                        _qrecord.Append(";");
                                                                    }
                                                                   // if (_debugflag > 6)
                                                                    //{
                                                                        sw.WriteLine("{0},{1},{2},{3},{4},{5},{6},{7},{8},{9},{10}",
                                                                        time.ToString("MM/dd/yyyy hh:mm:ss.fff"), protocol,ttl, len, srcIp, srcPort, dstIp, dstPort, response.IsAuthoritiveAnswer, response.ReturnCode.ToString(), _qrecord);
                                                                   // }                                                                
                                                                //}
                                                                _qrecord = null;
                                                            //}  
                                                        //}
                                                    }
                                                    tcpPacket = null;
                                                break;

                                              }  
                                       }
                                    //}   
                                }
                                catch (Exception ex)
                                {
                                    //System.Diagnostics.EventLog.WriteEntry("DNSCaptureService", "DNS Capture Service Startup: " + ex.Message + "\n\r" + ex.StackTrace);
                                    //Console.WriteLine(ex.StackTrace);
                                    //sw.WriteLine(ex.Message);
                                    sw.WriteLine(encoding.GetString(packet.Data));// + "\n\r" + ex.StackTrace);
                                } 
                            }
                            ourQueue = null;
                        //}
                    //}
                }
                
            }
        }

    }
}
